package com.varian.system.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.varian.mybatis.base.BaseServiceImpl;
import com.varian.mybatis.util.PageUtil;
import com.varian.rabbit.util.RabbitUtil;
import com.varian.system.constant.SystemConstant;
import com.varian.system.convert.SystemConvert;
import com.varian.system.dto.SysNoticeDTO;
import com.varian.system.dto.SysNoticePageDTO;
import com.varian.system.entity.SysNotice;
import com.varian.system.enums.NoticeReceiveTypeEnum;
import com.varian.system.enums.NoticeStatusEnum;
import com.varian.system.mapper.SysNoticeMapper;
import com.varian.system.mapper.SysUserMapper;
import com.varian.system.mapper.UserNoticeMapper;
import com.varian.system.service.ISysNoticeService;
import com.varian.system.vo.SysNoticeVO;
import com.varian.tool.enums.UserStatusEnum;
import com.varian.tool.exception.BizException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.Duration;
import java.time.LocalDateTime;
import java.util.List;

/**
 * @author ben
 * @since 2024/8/27
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class SysNoticeServiceImpl extends BaseServiceImpl<SysNoticeMapper, SysNotice> implements ISysNoticeService {

    private final SystemConvert baseConvert;
    private final UserNoticeMapper userNoticeMapper;
    private final SysUserMapper userMapper;

    @Override
    public boolean add(SysNoticeDTO dto) {
        boolean validUserReceiveType = !NoticeReceiveTypeEnum.USER.equals(dto.getNoticeReceiveType()) || CollUtil.isNotEmpty(dto.getNoticeReceiverIds());
        Assert.isTrue(validUserReceiveType, () -> BizException.of(SystemConstant.NOTICE_RECEIVER_IDS_NOT_EMPTY));
        SysNotice notice = baseConvert.toEntity(dto);
        processNoticeTimeAndStatus(notice);
        boolean result = save(notice);
        processNoticePublish(result, notice);
        return result;
    }

    private void processNoticePublish(boolean condition, SysNotice notice) {
        if (condition && !NoticeStatusEnum.EXPIRED.equals(notice.getNoticeStatus())) {
            LocalDateTime now = LocalDateTime.now();
            Duration sendDelay = LocalDateTimeUtil.between(now, notice.getNoticePublishTime());
            RabbitUtil.sendDelay(SystemConstant.ROUTE_NOTICE_SEND, notice.getId(), sendDelay.toMillis());
            if (notice.getNoticeExpireTime() != null) {
                Duration expireDelay = LocalDateTimeUtil.between(now, notice.getNoticeExpireTime());
                RabbitUtil.sendDelay(SystemConstant.ROUTE_NOTICE_EXPIRE, notice.getId(), expireDelay.toMillis());
            }
        }
    }

    @Override
    public Page<SysNoticeVO> page(SysNoticePageDTO dto) {
        Page<SysNotice> page = baseMapper.selectPage(dto.toPage(), lambdaQuery().getWrapper()
                .eq(dto.getNoticeReceiveType() != null, SysNotice::getNoticeReceiveType, dto.getNoticeReceiveType())
                .likeRight(StrUtil.isNotBlank(dto.getNoticeTitle()), SysNotice::getNoticeTitle, dto.getNoticeTitle())
                .ge(dto.getNoticePublishTimeStart() != null, SysNotice::getNoticePublishTime, dto.getNoticePublishTimeStart())
                .le(dto.getNoticePublishTimeEnd() != null, SysNotice::getNoticePublishTime, dto.getNoticePublishTimeEnd())
                .eq(dto.getNoticeStatus() != null, SysNotice::getNoticeStatus, dto.getNoticeStatus())
                .eq(StrUtil.isNotBlank(dto.getNoticeType()), SysNotice::getNoticeType, dto.getNoticeType())
                .orderByDesc(SysNotice::getId)
        );
        return PageUtil.convert(page, baseConvert::toVO);
    }

    @Override
    public SysNoticeVO detail(Long id) {
        SysNotice notice = validateExistence(id);
        return baseConvert.toVO(notice);
    }

    @Override
    public boolean modify(Long id, SysNoticeDTO dto) {
        SysNotice rawNotice = validateExistence(id);
        Assert.isTrue(NoticeStatusEnum.NOT_PUBLISHED.equals(rawNotice.getNoticeStatus()),
                () -> BizException.of(SystemConstant.NOTICE_CANNOT_BE_MODIFY));
        SysNotice notice = baseConvert.toEntity(id, dto);
        processNoticeTimeAndStatus(notice);
        return updateById(notice);
    }

    @Override
    public boolean delete(Long id) {
        SysNotice notice = validateExistence(id);
        Assert.isFalse(notice.getNoticeStatus().equals(NoticeStatusEnum.PUBLISHED),
                () -> BizException.of(SystemConstant.NOTICE_CANNOT_BE_DELETE));
        return removeById(id);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean publish(Long id) {
        SysNotice notice = validateExistence(id);
        Assert.isFalse(notice.getNoticeStatus().equals(NoticeStatusEnum.PUBLISHED), () -> BizException.of(SystemConstant.NOTICE_PUBLISHED));
        Assert.isFalse(notice.getNoticeStatus().equals(NoticeStatusEnum.EXPIRED), () -> BizException.of(SystemConstant.NOTICE_EXPIRED));
        notice.setNoticeStatus(NoticeStatusEnum.PUBLISHED).setNoticePublishTime(LocalDateTime.now());
        boolean result = updateById(notice);
        processNoticePublish(result, notice);
        return result;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean revoke(Long id) {
        SysNotice notice = validateExistence(id);
        Assert.isTrue(notice.getNoticeStatus().equals(NoticeStatusEnum.PUBLISHED), () -> BizException.of(SystemConstant.NOTICE_NOT_REVOKE));
        notice.setNoticeStatus(NoticeStatusEnum.NOT_PUBLISHED).setNoticePublishTime(null);
        boolean result = updateById(notice);
        if (result) {
            userNoticeMapper.deleteByNoticeId(id);
        }
        return result;
    }

    @Override
    public void processListenerNoticeSend(Long id) {
        getOptById(id).ifPresent(notice -> {
            if (NoticeStatusEnum.PUBLISHED.equals(notice.getNoticeStatus())) {
                Assert.isFalse(LocalDateTime.now().isBefore(notice.getNoticePublishTime()), () -> BizException.of("未到发布时间"));
                List<Long> userIds;
                if (notice.getNoticeReceiveType().equals(NoticeReceiveTypeEnum.ALL)) {
                    userIds = userMapper.selectIdBy(UserStatusEnum.ENABLE);
                } else {
                    userIds = notice.getNoticeReceiverIds();
                }
                userNoticeMapper.insertBatch(notice.getId(), userIds);
            }
        });
    }

    @Override
    public void processListenerNoticeExpire(Long id) {
        getOptById(id).ifPresent(notice -> {
            if (!notice.getNoticeStatus().equals(NoticeStatusEnum.EXPIRED)) {
                Assert.isFalse(LocalDateTime.now().isBefore(notice.getNoticeExpireTime()), () -> BizException.of("未到过期时间"));
                notice.setNoticeStatus(NoticeStatusEnum.EXPIRED);
                if (updateById(notice)) {
                    userNoticeMapper.deleteByNoticeId(id);
                }
            }
        });
    }

    @Override
    protected SysNotice validateExistence(Long id) {
        SysNotice notice = baseMapper.selectById(id);
        Assert.notNull(notice, () -> BizException.of(SystemConstant.NOTICE_NOT_EXIST));
        return notice;
    }

    private void processNoticeTimeAndStatus(SysNotice notice) {
        if (ObjUtil.isNull(notice.getNoticePublishTime())) {
//            发布时间必须小于过期时间最少24小时
            boolean validNoticeExpireTime = ObjUtil.isNull(notice.getNoticeExpireTime()) || notice.getNoticeExpireTime().isAfter(LocalDateTime.now().plusDays(1));
            Assert.isTrue(validNoticeExpireTime, () -> BizException.of(SystemConstant.NOTICE_EXPIRE_TIME_NOT_VALID));
        }
        notice.setNoticeStatus(NoticeStatusEnum.NOT_PUBLISHED);
    }
}
